import Foundation
import PeekabooCore
import PeekabooFoundation
import Testing
@testable import PeekabooCLI

private struct DialogTextFieldPayload: Codable, Sendable {
    let title: String?
    let value: String?
    let placeholder: String?
}

private struct DialogListPayload: Codable, Sendable {
    let title: String
    let role: String
    let buttons: [String]
    let textFields: [DialogTextFieldPayload]
    let textElements: [String]
}

#if !PEEKABOO_SKIP_AUTOMATION
@Suite(
    "Dialog Command  Tests",
    .serialized,
    .tags(.automation),
    .enabled(if: CLITestEnvironment.runAutomationRead)
)
struct DialogCommandTests {
    @Test("Dialog  command exists")
    func dialogCommandExists() {
        let config = DialogCommand.commandDescription
        #expect(config.commandName == "dialog")
        #expect(config.abstract.contains("Interact with system dialogs and alerts"))
    }

    @Test("Dialog  command has expected subcommands")
    func dialogSubcommands() {
        let subcommands = DialogCommand.commandDescription.subcommands
        #expect(subcommands.count == 5)

        var subcommandNames: [String] = []
        subcommandNames.reserveCapacity(subcommands.count)
        for descriptor in subcommands {
            guard let name = descriptor.commandDescription.commandName else { continue }
            subcommandNames.append(name)
        }
        #expect(subcommandNames.contains("click"))
        #expect(subcommandNames.contains("input"))
        #expect(subcommandNames.contains("file"))
        #expect(subcommandNames.contains("dismiss"))
        #expect(subcommandNames.contains("list"))
    }

    @Test("Dialog  click command help")
    func dialogClickHelp() async throws {
        let result = try await runCommand(["dialog", "click", "--help"])
        #expect(result.status == 0)
        let output = result.output

        #expect(output.contains("OVERVIEW: Click a button in a dialog using DialogService"))
        #expect(output.contains("--button"))
        #expect(output.contains("--window"))
        #expect(output.contains("--json-output"))
    }

    @Test("Dialog  input command help")
    func dialogInputHelp() async throws {
        let result = try await runCommand(["dialog", "input", "--help"])
        #expect(result.status == 0)
        let output = result.output

        #expect(output.contains("OVERVIEW: Enter text in a dialog field using DialogService"))
        #expect(output.contains("--text"))
        #expect(output.contains("--field"))
        #expect(output.contains("--index"))
        #expect(output.contains("--clear"))
    }

    @Test("Dialog  file command help")
    func dialogFileHelp() async throws {
        let result = try await runCommand(["dialog", "file", "--help"])
        #expect(result.status == 0)
        let output = result.output

        #expect(output.contains("OVERVIEW: Handle file save/open dialogs using DialogService"))
        #expect(output.contains("--path"))
        #expect(output.contains("--name"))
        #expect(output.contains("--select"))
    }

    @Test("Dialog  dismiss command help")
    func dialogDismissHelp() async throws {
        let result = try await runCommand(["dialog", "dismiss", "--help"])
        #expect(result.status == 0)
        let output = result.output

        #expect(output.contains("OVERVIEW: Dismiss a dialog using DialogService"))
        #expect(output.contains("--force"))
        #expect(output.contains("--window"))
    }

    @Test("dialog dismiss uses force flag")
    func dialogDismissForce() async throws {
        let dialogService = StubDialogService()
        dialogService.dismissResult = DialogActionResult(
            success: true,
            action: .dismiss,
            details: ["method": "escape"]
        )

        let services = self.makeTestServices(dialogs: dialogService)
        let (output, status) = try await self.runCommand(
            ["dialog", "dismiss", "--force", "--json-output"],
            services: services
        )

        #expect(status == 0)
        struct Payload: Codable {
            let success: Bool
            let data: DialogDismissResult
        }
        struct DialogDismissResult: Codable {
            let method: String
        }

        let response = try JSONDecoder().decode(Payload.self, from: Data(output.utf8))
        #expect(response.success == true)
        #expect(response.data.method == "escape")
    }

    @Test("Dialog  list command help")
    func dialogListHelp() async throws {
        let result = try await runCommand(["dialog", "list", "--help"])
        #expect(result.status == 0)
        let output = result.output

        #expect(output.contains("OVERVIEW: List elements in current dialog using DialogService"))
        #expect(output.contains("--json-output"))
    }

    @Test("Dialog  error handling")
    func dialogErrorHandling() {
        // Test that DialogError enum values are properly mapped
        let errorCases: [(PeekabooError, StandardErrorCode, String)] = [
            (.elementNotFound("OK"), .elementNotFound, "Element not found: OK"),
            (.invalidInput("Field index 5 out of range"), .invalidInput, "Invalid input: Field index 5 out of range"),
            (
                .operationError(message: "No text fields found in dialog."),
                .unknownError,
                "No text fields found in dialog."
            ),
        ]

        for (error, code, message) in errorCases {
            #expect(error.code == code)
            #expect(error.errorDescription == message)
        }
    }

    @Test("Dialog  service integration")
    @MainActor
    func dialogServiceIntegration() {
        // Verify that PeekabooServices includes the dialog service
        let services = self.makeTestServices()
        _ = services.dialogs // This should compile without errors
    }

    @Test("dialog list surfaces stubbed elements in JSON")
    func dialogListWithStubData() async throws {
        let elements = DialogElements(
            dialogInfo: DialogInfo(
                title: "Open",
                role: "AXWindow",
                subrole: "AXDialog",
                isFileDialog: true,
                bounds: .init(x: 0, y: 0, width: 400, height: 300)
            ),
            buttons: [
                DialogButton(title: "New Document"),
                DialogButton(title: "Open"),
            ],
            textFields: [
                DialogTextField(title: "Name", value: "", placeholder: "File name", index: 0, isEnabled: true),
            ],
            staticTexts: ["Choose a document to open"]
        )
        let dialogService = StubDialogService(elements: elements)
        let services = self.makeTestServices(dialogs: dialogService)

        let (output, status) = try await self.runCommand(
            ["dialog", "list", "--json-output"],
            services: services
        )
        #expect(status == 0)

        let data = try #require(output.data(using: .utf8))
        let response = try JSONDecoder().decode(CodableJSONResponse<DialogListPayload>.self, from: data)
        #expect(response.data.title == "Open")
        #expect(response.data.buttons.contains("New Document"))
        #expect(response.data.textFields.first?.placeholder == "File name")
    }

    @Test("dialog click emits JSON success when stub succeeds")
    func dialogClickJSON() async throws {
        let dialogService = await MainActor.run { StubDialogService() }
        dialogService.clickButtonResult = DialogActionResult(
            success: true,
            action: .clickButton,
            details: ["button": "New Document", "window": "Open"]
        )
        let services = self.makeTestServices(dialogs: dialogService)

        let (output, status) = try await self.runCommand(
            ["dialog", "click", "--button", "New Document", "--json-output"],
            services: services
        )
        #expect(status == 0)

        let data = try #require(output.data(using: .utf8))
        let response = try JSONDecoder().decode(JSONResponse.self, from: data)
        #expect(response.success == true)
        #expect(dialogService.recordedButtonClicks.count == 1)
        #expect(dialogService.recordedButtonClicks.first?.button == "New Document")
    }

    private struct CommandFailure: Error {
        let status: Int32
        let stderr: String
    }

    private func runCommand(_ args: [String]) async throws -> (output: String, status: Int32) {
        let services = self.makeTestServices()
        return try await self.runCommand(args, services: services)
    }

    private func runCommand(
        _ args: [String],
        services: PeekabooServices
    ) async throws -> (output: String, status: Int32) {
        let result = try await InProcessCommandRunner.run(args, services: services)
        let output = result.stdout.isEmpty ? result.stderr : result.stdout
        if result.exitStatus != 0 {
            throw CommandFailure(status: result.exitStatus, stderr: output)
        }
        return (output, result.exitStatus)
    }

    @MainActor
    private func makeTestServices(
        dialogs: any DialogServiceProtocol = StubDialogService()
    ) -> PeekabooServices {
        TestServicesFactory.makePeekabooServices(dialogs: dialogs)
    }
}

// MARK: - Dialog Command  Integration Tests

@Suite(
    "Dialog Command  Integration Tests",
    .serialized,
    .tags(.automation),
    .enabled(if: CLITestEnvironment.runAutomationActions)
)
struct DialogCommandIntegrationTests {
    @Test("List active dialogs with ")
    func listActiveDialogs() async throws {
        let output = try await runAutomationCommand([
            "dialog", "list",
            "--json-output",
        ])

        struct TextField: Codable {
            let title: String
            let value: String
            let placeholder: String
        }

        struct DialogListResult: Codable {
            let title: String
            let role: String
            let buttons: [String]
            let textFields: [TextField]
            let textElements: [String]
        }

        // Try to decode as success response first
        if let response = try? JSONDecoder().decode(
            CodableJSONResponse<DialogListResult>.self,
            from: Data(output.utf8)
        ) {
            if response.success {
                #expect(!response.data.title.isEmpty)
                #expect(!response.data.buttons.isEmpty)
            }
        } else {
            // Otherwise it's an error response
            let errorResponse = try JSONDecoder().decode(JSONResponse.self, from: Data(output.utf8))
            #expect(errorResponse.error?.code == "NO_ACTIVE_DIALOG")
        }
    }

    @Test("Dialog  click workflow")
    func dialogClickWorkflow() async throws {
        // This would click a button if a dialog is present
        let output = try await runAutomationCommand([
            "dialog", "click",
            "--button", "OK",
            "--json-output",
        ])

        let data = try JSONDecoder().decode(JSONResponse.self, from: Data(output.utf8))
        if !data.success {
            // Expected if no dialog is open
            #expect(data.error?.code == "NO_ACTIVE_DIALOG")
        }
    }

    @Test("Dialog  input workflow")
    func dialogInputWorkflow() async throws {
        let output = try await runAutomationCommand([
            "dialog", "input",
            "--text", "Test input",
            "--field", "Name",
            "--json-output",
        ])

        let data = try JSONDecoder().decode(JSONResponse.self, from: Data(output.utf8))
        if !data.success {
            // Expected if no dialog is open
            #expect(data.error?.code == "NO_ACTIVE_DIALOG")
        }
    }

    @Test("Dialog  dismiss with escape")
    func dialogDismissEscape() async throws {
        let output = try await runAutomationCommand([
            "dialog", "dismiss",
            "--force",
            "--json-output",
        ])

        struct DialogDismissResult: Codable {
            let action: String
            let method: String
            let button: String?
        }

        if let response = try? JSONDecoder().decode(
            CodableJSONResponse<DialogDismissResult>.self,
            from: Data(output.utf8)
        ) {
            if response.success {
                #expect(response.data.method == "escape")
            }
        }
    }

    @Test("File dialog  handling")
    func fileDialogHandling() async throws {
        let output = try await runAutomationCommand([
            "dialog", "file",
            "--path", "/tmp",
            "--name", "test.txt",
            "--select", "Save",
            "--json-output",
        ])

        struct FileDialogResult: Codable {
            let action: String
            let path: String?
            let name: String?
            let buttonClicked: String
        }

        // Try to decode as success response first
        if let response = try? JSONDecoder().decode(
            CodableJSONResponse<FileDialogResult>.self,
            from: Data(output.utf8)
        ) {
            if response.success {
                #expect(response.data.action == "file_dialog")
                #expect(response.data.path == "/tmp")
                #expect(response.data.name == "test.txt")
            }
        } else {
            // Otherwise it's an error response
            let errorResponse = try JSONDecoder().decode(JSONResponse.self, from: Data(output.utf8))
            #expect(errorResponse.error?.code == "NO_ACTIVE_DIALOG" || errorResponse.error?.code == "NO_FILE_DIALOG")
        }
    }
}

// MARK: - Test Helpers

private func runAutomationCommand(
    _ args: [String],
    allowedExitStatuses: Set<Int32> = [0, 1, 64]
) async throws -> String {
    let result = try await InProcessCommandRunner.runShared(
        args,
        allowedExitCodes: allowedExitStatuses
    )
    return result.combinedOutput
}
#endif
